<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */

namespace App\Service;

use App\Common\Base;
use GuzzleHttp\Exception\GuzzleException;

/**
 * binance.
 *
 * Class BinanceService
 */
class BinanceService extends Base
{
    //    public const BINANCE_URL = 'https://api.binance.com/';

    public const BINANCE_URL = 'https://testnet.binance.vision/';

    public array $data = [];

    /**
     * @param mixed $symbol
     * @param mixed $percentage
     * @param mixed $usdtAmount
     * @throws GuzzleException
     */
    public function adjustOrders($symbol, $percentage, $usdtAmount)
    {
        // 获取市场价格
        $priceData = $this->getPrice($symbol);
        $price = $priceData['price'];
        if (! $price) {
            $this->error(400);
        }
        // 设置买卖策略，比如：在当前价格上下 0.5% 挂单
        $buyPrice = bcmul($price, bcsub('1', bcdiv($percentage, '100', 8), 8), 8);
        $sellPrice = bcmul($price, bcadd('1', bcdiv($percentage, '100', 8), 8), 8);
        var_dump($price);
        var_dump($buyPrice);
        var_dump($sellPrice);

        // 提交买单和卖单
        //        $this->placeOrder($symbol, 'BUY', $quantity, $buyPrice);
        //        $this->placeOrder($symbol, 'SELL', $quantity, $sellPrice);
        return $this->placeOrderByUsdt($symbol, 'BUY', $usdtAmount, (float) $price);
    }

    public function getKlines(string $symbol, string $interval = '1m', int $limit = 20): array
    {
        $response = guzzle()->get(self::BINANCE_URL . 'api/v3/klines', [
            'query' => [
                'symbol' => strtoupper($symbol),
                'interval' => $interval,
                'limit' => $limit,
            ],
        ]);

        return json_decode((string) $response->getBody(), true);
    }

    public function placeOrderByUsdt(string $symbol, string $side, float $usdtAmount, float $price)
    {
        $quantity = round($usdtAmount / $price, 6); // 精度可根据币种调整

        $params = [
            'symbol' => strtoupper($symbol),
            'side' => strtoupper($side),
            'type' => 'LIMIT',
            'timeInForce' => 'GTC',
            'quantity' => $quantity,
            'price' => number_format($price, 8, '.', ''),
            'timestamp' => round(microtime(true) * 1000),
        ];
        try {
            $queryString = http_build_query($params);
            $signature = hash_hmac('sha256', $queryString, env('BINANCE_API_SECRET'));
            $response = guzzle()->post(self::BINANCE_URL . 'api/v3/order', [
                'headers' => [
                    'X-MBX-APIKEY' => env('BINANCE_API_KEY'),
                ],
                'query' => $params + ['signature' => $signature],
            ]);
        } catch (\Exception $exception) {
            $this->error(400, $exception->getMessage());
        }
        return json_decode($response->getBody()->getContents(), true);
    }

    // 修正数量（买入数量）
    public function adjustQuantity(string $symbol, float $quantity): string
    {
        $info = $this->getSymbolInfo($symbol);
        $stepSize = $info['LOT_SIZE']['stepSize'];

        $precision = $this->getPrecision($stepSize);
        return number_format(floor($quantity / $stepSize) * $stepSize, $precision, '.', '');
    }

    /**
     * @throws GuzzleException
     */
    public function getOrderBook(string $symbol, int $limit = 5): array
    {
        $query = http_build_query([
            'symbol' => strtoupper($symbol),
            'limit' => $limit,
        ]);

        $response = guzzle()->get(self::BINANCE_URL . "api/v3/depth?{$query}");

        return json_decode((string) $response->getBody(), true);
    }

    /**
     * 获取指定交易对的规则.
     *
     * @throws GuzzleException
     */
    public function getSymbolInfo(string $symbol): array
    {
        try {
            if (! isset($this->data[$symbol])) {
                $response = guzzle()->get(self::BINANCE_URL . '/api/v3/exchangeInfo', [
                    'query' => ['symbol' => strtoupper($symbol)],
                ]);

                $data = json_decode($response->getBody()->getContents(), true);
                $this->data[$symbol] = collect($data['symbols'][0]['filters'])->keyBy('filterType');
            }
        } catch (\Exception $exception) {
            $this->error(400, $exception->getMessage());
        }
        return $this->data[$symbol];
    }

    /**
     * 修正价格
     *
     * @throws GuzzleException
     */
    public function adjustPrice(string $symbol, float $price): string
    {
        $info = $this->getSymbolInfo($symbol);
        $tickSize = $info['PRICE_FILTER']['tickSize'];

        $precision = $this->getPrecision($tickSize);
        return number_format(floor($price / $tickSize) * $tickSize, $precision, '.', '');
    }

    /**
     * 获取币种价格
     * @param mixed $symbol
     * @return mixed
     * @throws GuzzleException
     */
    public function getPrice($symbol)
    {
        $request = guzzle()->get(self::BINANCE_URL . 'api/v3/ticker/price', [
            'query' => ['symbol' => $symbol],
        ]);
        $result = $request->getBody()->getContents();
        return json_decode($result, true);
    }

    // 获取小数位数
    protected function getPrecision(string $value): int
    {
        $parts = explode('.', rtrim($value, '0'));
        return isset($parts[1]) ? strlen($parts[1]) : 0;
    }

}
